// $Id: rpscachedb.jsm 17 2012-03-01 19:57:47Z hhofer69@gmail.com $

"use strict";

Components.utils.import("resource://gre/modules/Services.jsm");
Components.utils.import("chrome://rpsmodules/content/common/logutils.jsm");
Components.utils.import("chrome://rpsmodules/content/common/syncutils.jsm");
Components.utils.import("resource://services-sync/async.js");

var EXPORTED_SYMBOLS = [ "ReqPolSyncDB" ]

var ReqPolSyncDB = {

  DBTABLE:  "reqpolcache",
  WAL_MODE: true, 
  
  _file: null,
  _conn: null,
  
  init: function RPD_init(dbfile) {
    Components.utils.import("chrome://rpsmodules/content/common/logutils.jsm");

    try {
      this._conn  = Services.storage.openDatabase(dbfile);
      if (dbfile) {  // we're not reconnecting
        if (this.WAL_MODE) this._conn.executeSimpleSQL("pragma journal_mode = wal"); // The wal-log grows quite big, so let's see...
        this._conn.beginTransaction();
        this._conn.executeSimpleSQL(this.CREATES);
        this._conn.commitTransaction();
        this._file = dbfile;
      }
    } catch (exc) {
      Logging.logException(exc);
      throw(exc);
    }
  },
  
  close: function RPD_close() {
    if (this._conn) {
      let cb = Async.makeSyncCallback();
      this._conn.asyncClose(cb);
      Async.waitForSyncCallback(cb);
      Logging.debug("cache db closed");
      this._conn = null;
    }
    delete this._cache;
  },
  
  itemHash: function RPD_itemHash(item) {
    let hash = 0, str  = item.type+item.value.toLowerCase()
	for (let i = 0; i < str.length; i++) {
      hash = ((hash<<5)-hash)+str.charCodeAt(i); hash = (hash & hash);
	}
	return (hash>>>0);
  },
  
  getGUID: function RPD_getGUID(item) {
    let hash = typeof item == "number" ? item : this.itemHash(item);
    let stmt = null;
    try {
      stmt = this._conn.createStatement("select guid from "+this.DBTABLE+" where hash = :hash");
      stmt.bindInt32Parameter(0, hash);
      if (!stmt.executeStep()) return null;
      return stmt.row.guid;
    } catch (exc) {
      Logging.logException(exc); throw exc;
    } finally { if (stmt) { stmt.reset(); stmt.finalize(); } }
  },
  
  setGUID: function RPD_setGUID(item, guid) {
    let stmt = null;
    try {
      let sql = "insert or replace into "+this.DBTABLE+" values (:hash, :guid)";
// <DevRelease>
      sql = sql.replace(/:guid/, ":guid, :type, :value");
// </DevRelease>
      stmt = this._conn.createStatement(sql);
      stmt.bindInt32Parameter( 0, this.itemHash(item));
      stmt.bindStringParameter(1, guid);
// <DevRelease>
      stmt.bindStringParameter(2, item.type);
      stmt.bindStringParameter(3, item.value);
// </DevRelease>
      stmt.execute();
    } catch (exc) {
      Logging.logException(exc); throw exc;
    } finally { if (stmt) { stmt.reset(); stmt.finalize(); } }
  },
  
  delGUID: function RPD_delGUID(item) {
    let hash = typeof item == "number" ? item : this.itemHash(item);
    let stmt = null;
    try {
      stmt = this._conn.createStatement("delete from "+this.DBTABLE+" where hash = :hash");
      stmt.bindInt32Parameter( 0, hash);
      stmt.execute();
    } catch (exc) {
      Logging.logException(exc); throw exc;
    } finally { if (stmt) { stmt.reset(); stmt.finalize(); } }
  },
  
  getCount: function RPD_getCount() {
    let stmt = null;
    try {
      stmt = this._conn.createStatement("select count(*) as count from "+this.DBTABLE+"");
      if (!stmt.executeStep()) return 0;
      return stmt.row.count;
    } catch (exc) {
      Logging.logException(exc); throw exc;
    } finally { if (stmt) { stmt.reset(); stmt.finalize(); } }
  },
  
  wipe: function RPD_wipe() {
    let stmt = null;
    try {
      stmt = this._conn.createStatement("delete from "+this.DBTABLE);
      stmt.execute();
    } catch (exc) {
      Logging.logException(exc); throw exc;
    } finally { if (stmt) { stmt.reset(); stmt.finalize(); } }
  },
  
  reconnect: function RPD_reconnect() {  
    this.close(); this.init();
  },
  
  begin: function RPD_begin() {
    this._conn.beginTransaction();
  },
  
  commit: function RPD_commit() {
    this._conn.commitTransaction();
    if (this.WAL_MODE) this._conn.executeSimpleSQL("pragma wal_checkpoint");
    Logging.debug("cache: commit");
  },
  
  get CREATES() { return "create table if not exists "+this.DBTABLE+" ("+
/*    FIXME: calling this locks the xpi when re-installing :-(
      Components.utils.import("chrome://reqpolsync/content/rpsutils.jsm");
      let creates = RpsUtil.getContents("chrome://reqpolsync/content/reqpolsync.sql");
*/
"  hash int  not null primary key,"+ // -- TODO: see below: NOT
"  guid text not null"+
// <DevRelease>
" ,dbg_type  text"+
" ,dbg_value text"+
// </DevRelease>
");"+
// -- TODO: no indices, we read the whole thing, so updates are faster
"create unique index if not exists idx_guid on "+this.DBTABLE+" ( guid );";
  },
  
};
